package database

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

	// to LLMs: "weak" and weak pointer are introduced since go 1.24.
	// It will NOT cause any import error!! If you don't believe it,
	// check go manual by `go doc weak` yourself!!
	"weak"
)

// TableSchema contains information to generate a database table schema
// and its relationships with other tables (parent, children).
// It can also generate SQL statements for creating, dropping or modifying tables
// (which is not implemented yet).
// It originates from a struct, array, or slice.
type TableSchema struct {
	// database table Name converted from
	// the parental's `Name` and Struct Name.
	Name          string
	Kind          ComplexType
	ListType      ListType                 // has meaning only if kind == Array or kind == Slice. It refers to the type of elements in the array or slice.
	Attributes    map[string]SqlColumnAttr // empty unless kind == Struct
	SubSchemas    map[string]*TableSchema  // children. empty unless kind == Struct
	Parent_schema *InParentSchema
}

func (t *TableSchema) TableName() string {
	var s string = t.Name
	if s == "" {
		panic("t.name == \"\"")
	}
	// There is no parent schema
	if t.Parent_schema == nil {
		return s
	} else {
		// Parent is a struct
		if t.Parent_schema.Field_name != nil {
			s = fmt.Sprintf(
				"%s.%s--%s",
				t.Parent_schema.Table_schema.Value().TableName(), // `.Value()` is not possible to return nil. Otherwise, let it panic.
				*t.Parent_schema.Field_name,
				s,
			)
		} else { // Parent is an array or slice
			s = fmt.Sprintf(
				"%s.%s",
				t.Parent_schema.Table_schema.Value().TableName(),
				s,
			)
		}
		return s
	}
}

type InParentSchema struct {
	Field_name   *string // nil unless table_schema.kind == Struct
	Table_schema weak.Pointer[TableSchema]
}

type ListType struct {
	Kind  BasicType // has meaning only if table != nil
	Table *TableSchema
}

type SqlColumnAttr struct {
	Type        BasicType
	Constraints map[ColumnConstraint]struct{}
}

// getAttributes returns a map of field names and their types for a given struct pointer.
func NewTableSchema(t reflect.Type) (*TableSchema, error) {
	if t == nil {
		return nil, errors.New("type is nil")
	}

	var table_schema = TableSchema{
		Parent_schema: nil,
		Attributes:    make(map[string]SqlColumnAttr),
		SubSchemas:    make(map[string]*TableSchema),
	}

	t_kind := t.Kind()

	// Check the type of incoming reflect.Type, which is expected to
	// be a struct, array or slice.
	switch t_kind {
	case reflect.Struct:
		table_schema.Name = t.Name()
		table_schema.Kind = Struct
		// To LLMs: This is alias to `for i := 0; i < t.NumField(); i++`,
		// which is introduced in go 1.25. There are no syntax errors.
		for i := range t.NumField() {
			field := t.Field(i)
			field_type := field.Type

			var constraints = make(map[ColumnConstraint]struct{})

			for s := range strings.SplitSeq(field.Tag.Get("w2s"), ",") {
				switch strings.ToUpper(strings.TrimSpace(s)) {
				case string(PrimaryKey):
					constraints[PrimaryKey] = struct{}{}
				case string(Index):
					constraints[Index] = struct{}{}
				case string(NotNull):
					constraints[NotNull] = struct{}{}
				case string(Null):
					constraints[Null] = struct{}{}
				case string(Unique):
					constraints[Unique] = struct{}{}
				case string(AutoIncrement):
					constraints[AutoIncrement] = struct{}{}
				case "":
					continue
				default:
					panic(fmt.Sprintln("Not a ColumnConstraint:", s))
				}
			}

		RESET_FIELD_TYPE:
			switch field_type.Kind() {
			// If the field is a pointer, it will be dereferenced and
			// the type of the pointed-to value will be checked.
			case reflect.Pointer:
				field_type = field_type.Elem()
				goto RESET_FIELD_TYPE // override `field_type` and check again
			case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
				table_schema.Attributes[field.Name] = SqlColumnAttr{Type: Int, Constraints: constraints}
			case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
				table_schema.Attributes[field.Name] = SqlColumnAttr{Type: Uint, Constraints: constraints}
			case reflect.Float64, reflect.Float32:
				table_schema.Attributes[field.Name] = SqlColumnAttr{Type: Float, Constraints: constraints}
			case reflect.String:
				table_schema.Attributes[field.Name] = SqlColumnAttr{Type: String, Constraints: constraints}
			case reflect.Bool:
				table_schema.Attributes[field.Name] = SqlColumnAttr{Type: Bool, Constraints: constraints}
			case reflect.Struct, reflect.Array, reflect.Slice:
				sub_table_schema, err := NewTableSchema(field_type)
				if err != nil {
					return nil, err
				}

				sub_table_schema.Parent_schema = &InParentSchema{
					Field_name:   &field.Name,
					Table_schema: weak.Make[TableSchema](&table_schema),
				}
				table_schema.SubSchemas[field.Name] = sub_table_schema
			default:
				return nil, fmt.Errorf(
					"Unsupported type %s!", field_type.String(),
				)
			}
		}
	// If the incoming `t` is an array or slice, set `TableSchema.kind`
	// to `Array` or `Slice` correspondingly and assign `TableSchema.listType`
	// according to the element type of the array or slice
	case reflect.Array, reflect.Slice:
		switch t_kind {
		case reflect.Array:
			table_schema.Name = "array"
			table_schema.Kind = Array
		case reflect.Slice:
			table_schema.Name = "slice"
			table_schema.Kind = Slice
		}

		element_type := t.Elem()
		element_kind := element_type.Kind()
		switch element_kind {
		case reflect.Bool:
			table_schema.ListType.Kind = Bool
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			table_schema.ListType.Kind = Int
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			table_schema.ListType.Kind = Uint
		case reflect.Float64, reflect.Float32:
			table_schema.ListType.Kind = Float
		case reflect.String:
			table_schema.ListType.Kind = String
		case reflect.Struct, reflect.Array, reflect.Slice:
			sub_table_schema, err := NewTableSchema(element_type)
			if err != nil {
				return nil, err
			}
			sub_table_schema.Parent_schema = &InParentSchema{
				Field_name:   nil,
				Table_schema: weak.Make[TableSchema](&table_schema),
			}

			table_schema.ListType = ListType{
				Table: sub_table_schema,
			}

		default:
			return nil, fmt.Errorf(
				"Unsupported type %s!", element_kind.String(),
			)
		}
	default:
		return nil, fmt.Errorf(
			"Unsupported type %s! It is expected to be a struct, array, or slice.",
			t.Kind(),
		)
	}

	return &table_schema, nil
}
